﻿#region File Description
/*-----------------------------------------------------------------------------
 * Class: Employee
 *
 * Employee is a DrawableGameComponent and a base class for ...
 * All employees have needs... can be instructed...
 *
 *
 * Notes: Employee will be a base class for specific employees, if we get there.
 -------------------------------------------------------------------------------*/
#endregion

using System;
using System.Collections.Generic;
using Cubezicles.NeedObjects;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Cubezicles
{
    public struct MoodLevels
    {
        private float _hunger, _bladder, _focus, _fun, _energy;
        public float hunger
        {
            get { return _hunger; }
            set { _hunger = MathHelper.Clamp(value, 0f, 1f); }
        }

        public float bladder
        {
            get { return _bladder; }
            set { _bladder = MathHelper.Clamp(value, 0f, 1f); }
        }

        public float focus
        {
            get { return _focus; }
            set { _focus = MathHelper.Clamp(value, 0f, 1f); }
        }

        public float fun
        {
            get { return _fun; }
            set { _fun = MathHelper.Clamp(value, 0f, 1f); }
        }

        public float energy
        {
            get { return _energy; }
            set { _energy = MathHelper.Clamp(value, 0f, 1f); }
        }

        //
        // TODO: make some sort of function that allows more readable rates?
        // TODO: make constants for decay rate values
        //

        public static MoodLevels DefaultDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.00028f/250,
                    //good-ish
                    bladder = 0.00033f/250,
                    focus = 0.0002f/250,
                    // think this is a good rate! Yeah, their focus is gooooone by 3, lol
                    fun = 0.00024f/250,
                    energy = 0.00015f/250
                };
            }
        }

        #region StateBasedDecayRates

        // In comments, "increase" refers to getting closer to 100%, decrease refers to getting closer to 0%
        // (can be confusing when talking about increasing bladder by going to bathroom)
        // Also, negative values for increasing needs because they are DECAY rates and are subtracted

        // todo: create constants or enums for decay rate levels....
        // also todo: these will all become decay rates associated with objects....

        public static MoodLevels IdleDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0f / 250,
                    bladder = 0f /250,
                    focus = 0f / 250,
                    fun = 0f / 250,
                    energy = 0f / 250
                };
            }
        }

        public static MoodLevels AtHomeDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = -0.0015f / 250,  // todo: maybe change these??
                    bladder = -0.0015f / 250,
                    focus = -0.0015f / 250,
                    fun = -0.0015f / 250,
                    energy = -0.0015f / 250
                };
            }
        }

        public static MoodLevels EatingDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = -0.002f / 250,     // hunger:  strong increase
                    bladder = 0.0004f / 250,    // bladder: slight decrease
                    focus = -0.0002f / 250,     // focus:   slight increase
                    fun = 0.0f / 250,           // fun:     no change
                    energy = -0.0002f / 250     // energy:  slight increase
                };
            }
        }

        public static MoodLevels BathroomingDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.0f / 250,        // hunger:  no change
                    bladder = -0.01f / 250,     // bladder: strong increase
                    focus = -0.00015f / 250,    // focus:   slight increase
                    fun = 0.0f / 250,           // fun:     no change
                    energy = 0.0f / 250         // energy:  no change
                };
            }
        }

        public static MoodLevels HavingFunDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.0002f / 250,    // slightly less than normal hunger decrease
                    bladder = 0.00033f / 250,   // normal bladder decrease
                    focus = 0.0002f / 250,      // todo
                    fun = -0.002f / 250,       // todo....
                    energy = 0.00015f / 250     // todo
                };
            }
        }

        public static MoodLevels EnergizingDecayRates
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.00018f / 250,    // hunger:  slight decrease
                    bladder = 0.0004f / 250,    // bladder: moderate decrease
                    focus = -0.0002f / 250,     // focus:   moderate increase
                    fun = -0.0001f / 250,       // fun:     slight increase
                    energy = -0.0008f / 250     // energy:  increase
                };
            }
        }


        public static MoodLevels TakingABreakDecayRates // not sure if we'll use this one...probs not actually
        {
            get
            {
                return new MoodLevels()
                {
                    hunger = 0.00028f / 250,    // todo
                    bladder = 0.00033f / 250,   // todo
                    focus = 0.0002f / 250,      // todo.....
                    fun = 0.00024f / 250,       // todo
                    energy = 0.00015f / 250     // todo
                };
            }
        }

        // todo: also need an intermediate state, for being on their way to do something?!??***

        #endregion
    }

    public class Employee : ClickableGameComponent
    {
        // Employee faces at this angle in the beginning
        private static readonly Quaternion startOrientation =
            Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.PiOver4 * 3);

        // Distance from the origin to the employee's workstation
        public static readonly Vector3 CubeOffset = new Vector3(5, 0, -5);

        // todo: states might be changed to be based on object they are using?! in addition to working, idle, athome
        // The current state of the Employee (what they are currently doing)
        // Used to determine which ...
        // change TakingABreak to relate to focus? ***... don't think we'll even use it??? should improve focus...
        public enum State { Idle = 0, AtHome, Working, TakingABreak, Eating, Bathrooming, HavingFun, Energizing } 

        public State currentState = State.Idle;    // Current Employee state

        //Face texture (to be called from employee manager
        Texture2D face; 

        // Employee's home cubicle
        public Cubicle cubicle;     // temp setting this to public, because when fire need to get rid of employee AND their cubicle.... need to think of a better way to do this

        //-- Employee "needs" - measured on a meter of 0 to 1 --//
        private MoodLevels moodLevels;
        public MoodLevels MoodLevels { get { return moodLevels; } }

        // Rate of decay for those needs
        private MoodLevels decayRates = MoodLevels.DefaultDecayRates;


        // The overall mood (a calculation of all of their current need levels).... in progress
        public float OverallMood { get; private set; }


        //--- Public getters/setters for member variables ---//
        public string Name { get; private set; }

        // The employee ID of this employee (used by EmployeeManager)
        public int ID { get; set; }

        // Return whether this employee is the currently selected one
        // the currently selected employee is handled by the game object
        public new Boolean IsSelected { get { return Game1.CurrentEmployee == this; } }

        // Get the Game1 object stored by the base class
        private Game1 game { get { return (Game1)Game; } }

        //...................................
        private TimeSpan dailyTimeSpentWorking;

        // not yet implemented, plans for future:
        // private int qualityOfWork

        /// <summary>
        /// Employee class constructor.
        /// </summary>
        public Employee(Game1 game, String name, int id, Cubicle cube)
            : base(game, cube.Position + CubeOffset, "Models/Employees/melvin")
        {
            this.DrawOrder = (int)Game1.DisplayOrder.Objects;
            // Save these parameters for later
            cubicle = cube;
            Name = name;
            ID = id;

            Orientation = startOrientation;
            Scaling = Vector3.One * 2;
            
            if (ID > 3) // why > 3?
            {
                Orientation *= Rotate90CW();
                Position = cube.Position + Vector3.Transform(CubeOffset, Rotate90CW());
            }

            // Set all Need levels to 100% initially, and time worked in day so far to 0
            ResetEmployee();
        }


        /// <summary>
        /// Start of a new day - Set all employee Need levels to 100%/full,
        /// and set time worked in day so far to 0, and state = working
        /// </summary>
        public void ResetEmployee()
        {
            moodLevels = new MoodLevels()
            {
                hunger = 1f,
                fun = 1f,
                energy = 1f,
                bladder = 1f,
                focus = 1f
            };

            // also reset this.... todo change method title (??)
            dailyTimeSpentWorking = new TimeSpan(0);

            currentState = State.Working;
        }

        /// <summary>
        /// Load content for the Employee
        /// </summary>
        protected override void LoadContent()
        {
            face = Game.Content.Load<Texture2D>("Models/Employees/face"); // todo: can we make the faces/employees better lit?!?
            base.LoadContent();
        }

        public override void Update(GameTime gameTime)
        {
            // TEMPORARILY use number keys 4-9 + 0 to toggle between employee states *** //
            // keys 1-3 are for controlling speed!!
            KeyboardState keys = Keyboard.GetState();

            // 0 = idle
            if(IsSelected) // only change state of employee if they are currently the selected one
            {
                if (keys.IsKeyDown(Keys.D0)) currentState = State.Idle;
                else if (keys.IsKeyDown(Keys.D4)) currentState = State.Energizing;
                else if (keys.IsKeyDown(Keys.D5)) currentState = State.Working;
                else if (keys.IsKeyDown(Keys.D6)) currentState = State.Eating;
                else if (keys.IsKeyDown(Keys.D7)) currentState = State.HavingFun;
                else if (keys.IsKeyDown(Keys.D8)) currentState = State.Bathrooming;
                else if (keys.IsKeyDown(Keys.D9)) currentState = State.AtHome;
            }

            // *** Reference cheaty (and legit) keys: ***
            // m = add money to current funds
            // n = remove money from current funds
            // g = move current employee left
            // h = move current employee right
            // tab = toggle between currently selected employees
            // 1 = speed play
            // 2 = speed F
            // 3 = speed FF
            // P = pause/unpause
            // 4 = selected worker -> taking a break
            // 5 = selected worker -> working ***
            // 6 = selected worker -> eating
            // 7 = selected worker -> having fun
            // 8 = selected worker -> bathrooming
            // 9 = selected worker -> at home --> todo: if at home, shouldn't draw in 3d world!!!
            // 0 = selected worker -> idle


            //--- Different decay rates based on current State of employee ---//

            // Get the decay rates based on the current State of employee
            // if we had more objects, this would probably go through an object... ***** Steve todo
            // ...but we only have one object per need, and one state per need, plus a couple
            
            // if (UpdateEmployeeState())decayRates = UpdateDecayRates(); // this didn't work, dunno why...

            UpdateEmployeeState();
            UpdateDecayRates();

            if(currentState == State.Working)
            {
                // todo: Yee: please explain how this works....??? it does work...
                dailyTimeSpentWorking = dailyTimeSpentWorking.Add(
                    new TimeSpan(gameTime.ElapsedGameTime.Ticks * (int)game.time.CurrTimeSpeed));
            }

            
            // Update the current need levels of the employee with the current decay rates
            // Prevent from going negative or exceeding 100%
            moodLevels.hunger -= (float)game.time.CurrTimeSpeed * decayRates.hunger;
            moodLevels.fun -= (float)game.time.CurrTimeSpeed * decayRates.fun;
            moodLevels.energy -= (float)game.time.CurrTimeSpeed * decayRates.energy;
            moodLevels.bladder -= (float)game.time.CurrTimeSpeed * decayRates.bladder;
            moodLevels.focus -= (float)game.time.CurrTimeSpeed * decayRates.focus;

            // Update the overall mood (used for drawing background color
            // + possibly facial expression of avatar profile image)... getFace()
            CalculateOverallMood();

            base.Update(gameTime);
        }




        // todo: testing.... method to check if needs fulfilled, if not, change state....?
        /// <summary>
        /// Updates the state of the employee.
        /// </summary>
        /// <returns></returns>
        private bool UpdateEmployeeState()
        {
            // returns whether or not there was a change in employee's state
            // don't actually think this works?.....
            bool changeInState = false;
            
            // the limit that employees will change their state and
            // try to fulfill a need if it drops below this level
            float levelLimit = .3f; //low
            // also have a "critical" limit???

            string pinball = "Pinball Machine";
            string vend = "Vending Machine";
            string bathroom = "Bathroom";
            string drink = "Dew Machine";

            ItemManager im = game.ItemManager;

            switch (currentState)
            {
                //case State.Idle:
                  //  return MoodLevels.IdleDecayRates;
              //  case State.AtHome:
                //    return MoodLevels.AtHomeDecayRates;
               
               // case State.TakingABreak:
                 //   return MoodLevels.TakingABreakDecayRates;
                case State.Eating:
                    // check if hunger need is fully satisfied
                    if (moodLevels.hunger >= 1) 
                    {
                        // if it is, they stop using the need object
                        NeedObject n = (NeedObject)im.GetItem<VendingMachine>(); // invalid cast exception unhandled***
                        n.SetUser(null);
                        //... and go back to work
                        // todo: this makes it too easy! workers should be reluctant to go back to work....***
                        currentState = State.Working;
                        changeInState = true; //return true; //changeInState = true;
                    }
                    break;
                case State.HavingFun:
                    if (moodLevels.fun >= 1)
                    {
                        currentState = State.Working;
                        changeInState = true;
                    }
                    break;
                case State.Bathrooming:
                    if (moodLevels.bladder >= 1)
                    {
                        currentState = State.Working;
                        im.GetItem<Bathroom>().ClearUser();
                        changeInState = true;
                    }
                    moodLevels.bladder += 0.05f;
                    break;
                case State.Energizing:
                    if (moodLevels.energy >= 1)
                    {
                        currentState = State.Working;
                        changeInState = true;
                    }
                    break;
                case State.Working:
                    break;
                    //   return MoodLevels.DefaultDecayRates;
                    //default:
                    //  return ;
            }

            // originally these were only in working, but these should be checked
            // no matter what the current state of employee is
            
            // overview of following code idea (not yet implemented fully) - lizz
            // check if the employee's need is low
            // if it is, check if an item to fulfill this need exists in the workplace (a NeedsObject)
            // then, check if the item is already in use
            // if not, stop working and go fulfill your need
            //                      until your need is fulfilled (or something else happens, like instructed to go back to work)
            // otherwise.... keep working, until a need (any need) hits rock bottom, then you go home for the day...??

            /* // name options: for lizz's naughty hack method
            name = "Bathroom";
            name = "Dew Machine";
            name = "Pinball Machine";
            name = "Vending Machine";
             */
            

            // the employee's hunger need is low
            if (moodLevels.hunger <= levelLimit)
            {
                // check if an item to fulfill their hunger need exists (vending machine
                var n = im.GetItem<VendingMachine>();

                // if it exists, check if it is in use
                if(n != null && n.CurrentUser == null)
                {
                    // if not, stop working, and go use the object
                    currentState = State.Eating;
                    n.SetUser(this);
                    changeInState = true;
                }
                // else....??
            }

            // *****should these be ifs, or else-ifs....? what need is most important for them to satisfy??

            if (moodLevels.energy <= levelLimit)
            {
                currentState = State.Energizing;
                changeInState = true;
            }

            if (moodLevels.bladder <= levelLimit)
            {
                var n = im.GetItem<Bathroom>();
                // if it exists, check if it is in use
                if (n != null && n.CurrentUser == null)
                {
                    // if not, stop working, and go use the object
                    currentState = State.Bathrooming;
                    n.SetUser(this);
                    changeInState = true;
                }
            }

            if (moodLevels.fun <= levelLimit)
            {
                currentState = State.HavingFun;
                changeInState = true;
            }

            //if (changeInState) UpdateDecayRates();
            //    decayRates = UpdateDecayRates();

            // whether or not the state of this employee changed..... dont think this works out
            return changeInState;
        }
        
        /// <summary>
        /// Update this employee's need decay rates based on their current State
        /// </summary>
        private void UpdateDecayRates()
        {
            switch (currentState)
            {
                case State.Idle:
                    decayRates = MoodLevels.IdleDecayRates;
                    break;
                case State.AtHome:
                    decayRates = MoodLevels.AtHomeDecayRates;
                    break;
                case State.Working:
                    decayRates = MoodLevels.DefaultDecayRates;
                    break;
                case State.Eating:
                    decayRates = MoodLevels.EatingDecayRates;
                    break;
                case State.HavingFun:
                    decayRates = MoodLevels.HavingFunDecayRates;
                    break;
                case State.Bathrooming:
                    decayRates = MoodLevels.BathroomingDecayRates;
                    break;
                case State.Energizing:
                    decayRates = MoodLevels.EnergizingDecayRates;
                    break;
                case State.TakingABreak: // should probs remove...
                    decayRates = MoodLevels.TakingABreakDecayRates;
                    break;
                default:
                    decayRates = MoodLevels.DefaultDecayRates;
                    break;
            }
        }

        /// <summary>
        /// Calculate the overall mood of this employee based on their current need status levels.
        /// </summary>
        private void CalculateOverallMood()
        {
            // Just do a simple average of need levels to calculate overall mood
            OverallMood = (MoodLevels.hunger + MoodLevels.fun + MoodLevels.energy + MoodLevels.bladder + MoodLevels.focus) / 5f;
        }

        /// <summary>
        /// at end of day, used to report how many hours this particular employee spent working
        /// used to generate revenue for day
        /// </summary>
        /// <returns></returns>
        public int GetDailyTimeSpentWorking()
        {
            // This just returns hours. May want to adjust to return something idk, more comprehensive ***
            return dailyTimeSpentWorking.Hours;
        }


        /// <summary>
        /// Convert the current state to a string
        /// </summary>
        /// <returns>the employee's current state in printable string form</returns>
        public string GetStateString()
        {
            switch (currentState)
            {
                case State.Idle:
                    return "Idle";
                case State.AtHome:
                    return "At Home";
                case State.Working:
                    return "Working";
                case State.TakingABreak:
                    return "Taking a Break";
                case State.Eating:
                    return "Eating";
                case State.HavingFun:
                    return "Having Fun";
                case State.Bathrooming:
                    return "Bathroom-ing";
                case State.Energizing:
                    return "Energizing";
                default:
                    return "Unknown";
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Texture2D getFace()
        {
            //Idk, maybe we want to process the face some more first. I'll leave this as a function for now. 
            // adding facial expressions if we get time
            // will use overall mood to determine
            return face;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool GetBackToWork()
        {
            currentState = State.Working;

            // todo: theres probably  more to finishing get back to work...
            // is there eveb a way that it would return false, they would refuse to get back to work???
            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        public void GoHome() // shouldnt draw anymore after they go home!!!
        {
            currentState = State.AtHome;
            // todo: theres probably  more to finishing go home.... shouldnt draw any more for one
        }
    }
}
